
//#define DEBUG_TEST
/*******************************************************************************
*	HEADER FILES
*/
#include <linux/module.h>
#include <linux/version.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/gpio.h>

#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
//#include <asm/io.h>

#ifndef DEBUG_TEST
#include <mach/io.h>
#include <mach/cputype.h>
#include <mach/hardware.h>
//#include "globaldef.h"
#endif

/*******************************************************************************
*	LOCAL DEFINES
*/

#define DEVICE_NAME "spi_ww"
#define SPI_MAJOR 230

#ifndef DEBUG_TEST
#define __REG(addr)   (*(volatile unsigned int *)IO_ADDRESS(addr))
#define REG_PINMUX0     __REG(0x01c40000)
#define REG_PINMUX1     __REG(0x01c40004)
#define REG_PINMUX2     __REG(0x01c40008)
#define REG_PINMUX3     __REG(0x01c4000c)
#define REG_PINMUX4     __REG(0x01c40010)
#endif

#define SCK_LOOP 	100
#define BUFSIZE		32

enum SPI_CTRL_CMD
{
  SPI_SET_kHZ = 10,
  SPI_SET_CPOL,
  SPI_SET_CPHA,
  SPI_SET_DELAY,
  NFPGA_RST,
  NDSP_RST,
  FPGA_RTRIG,
  SPI_CLK,
  IRQ_TURN,
  SPI_SET_WR
};
/*******************************************************************************
*	FILE GLOBALS
*/
static int gpio_spi_cs       = 23;

static int gpio_spi_clk      = 87;
static int gpio_spi_dout     = 85;
static int gpio_spi_din      = 28;//34;

static int gpio_nDsp_rst     = 86;
static int gpio_nFpga_rst    = 91;
static int gpio_Fpga_rtrig   = 92;
static int gpio_nFpga_rdata  = 36;
static int gpio_wr_pcb_frame = 90;
static int gpio_rd_pcb_frame = 88;
static int gpio_nsol_frame   = 33;

static int spi_delay_us   = 3;
static int spi_cpol       = 0;
static int spi_cpha       = 1;
static int spi_byte_delay = 3;
static unsigned long   irq_flags = 0;
static unsigned char spi_WR = 0;

static unsigned int spi_major = SPI_MAJOR;
static unsigned int spi_minor = 0;
static struct cdev spi_dev;
unsigned char recv_buf[BUFSIZE];
unsigned char send_buf[BUFSIZE];
static struct class *spidev_class;

/*delay time unit: us */
void spiDelay(unsigned int sck)
{
  int i=0;
  udelay(sck);
}
int spi_gpio_init(void)
{
  int status = 0;
#ifndef DEBUG_TEST
  /* request the gpios */

  status = gpio_request(gpio_spi_cs, DEVICE_NAME);
  if(status<0)
  {    
    printk("<1>""the spi gpio %d is busy!\n",gpio_spi_cs);
    return status;
  }
  status = gpio_request(gpio_spi_clk, DEVICE_NAME);
  if(status<0)
  {    
    printk("<1>""the spi gpio %d is busy!\n",gpio_spi_clk);
    return status;
  }

  status = gpio_request(gpio_spi_dout, DEVICE_NAME);
  if(status<0)
  {    
    printk("<1>""the spi gpio %d is busy!\n",gpio_spi_dout);
    return status;
  }

  status = gpio_request(gpio_spi_din, DEVICE_NAME);
  if(status<0)
  {    
    printk("<1>""the spi gpio %d is busy!\n",gpio_spi_din);
    return status;
  }
  status = gpio_request(gpio_nFpga_rdata, DEVICE_NAME);
  if(status<0)
  {    
    printk("<1>""the nFpga_rdata gpio %d is busy!\n",gpio_nFpga_rdata);
    return status;
  }
  status = gpio_request(gpio_nFpga_rst, DEVICE_NAME);
  if(status<0)
  {    
    printk("<1>""the nFpga_rst gpio %d is busy!\n",gpio_nFpga_rst);
    return status;
  }
  status = gpio_request(gpio_nDsp_rst, DEVICE_NAME);
  if(status<0)
  {    
    printk("<1>""the nDsp_rst gpio %d is busy!\n",gpio_nDsp_rst);
    return status;
  }
  status = gpio_request(gpio_Fpga_rtrig, DEVICE_NAME);
  if(status<0)
  {    
    printk("<1>""the Fpga_rtrig gpio %d is busy!\n",gpio_Fpga_rtrig);
    return status;
  }
  status = gpio_request(gpio_wr_pcb_frame, DEVICE_NAME);
  if(status<0)
  {    
    printk("<1>""the gpio_wr_pcb_frame %d is busy!\n",gpio_wr_pcb_frame);
    return status;
  }
  status = gpio_request(gpio_rd_pcb_frame, DEVICE_NAME);
  if(status<0)
  {    
    printk("<1>""the gpio_rd_pcb_frame %d is busy!\n",gpio_rd_pcb_frame);
    return status;
  }
  status = gpio_request(gpio_nsol_frame, DEVICE_NAME);
  if(status<0)
  {    
    printk("<1>""the gpio_nsol_frame %d is busy!\n",gpio_nsol_frame);
    return status;
  }

  /* gpio pins mux set */

  REG_PINMUX3 &= ~(1 << 27);  //GIO23 used as gpio
  REG_PINMUX3 &= ~(1 << 26);

  REG_PINMUX4 &= ~(1 << 3);  //GIO28 used as gpio
  REG_PINMUX4 &= ~(1 << 2);

  REG_PINMUX4 &= ~(1 << 13);  //GIO33
  REG_PINMUX4 &= ~(1 << 12);  //GIO33
  //REG_PINMUX4 &= ~(1 << 15);  //GIO34
  //REG_PINMUX4 &= ~(1 << 14);  //GIO34
  REG_PINMUX4 &= ~(1 << 19);  //GIO36
  REG_PINMUX4 &= ~(1 << 18);  //GIO36

  REG_PINMUX1 &= ~(0xffff);  //GIO85-92 used as gpio

  /* gpio direction set */

  gpio_direction_output(gpio_spi_cs, 0);
  gpio_direction_output(gpio_spi_clk, 0);
  gpio_direction_output(gpio_spi_dout, 0);
  gpio_direction_output(gpio_nDsp_rst, 0);
  gpio_direction_output(gpio_nFpga_rst, 0);
  gpio_direction_output(gpio_Fpga_rtrig, 0);
  gpio_direction_output(gpio_wr_pcb_frame, 0);
  gpio_direction_output(gpio_rd_pcb_frame, 0);
  gpio_direction_output(gpio_nsol_frame, 0);
  gpio_direction_input(gpio_spi_din);
  gpio_direction_input(gpio_nFpga_rdata);


  /* gpio output set 1*/
  gpio_set_value(gpio_spi_cs, 1);
  gpio_set_value(gpio_spi_clk, 1);
  gpio_set_value(gpio_spi_dout, 1);
  gpio_set_value(gpio_nDsp_rst, 0);
  gpio_set_value(gpio_nFpga_rst, 0);
  gpio_set_value(gpio_Fpga_rtrig, 1);
  gpio_set_value(gpio_wr_pcb_frame, 1);
  gpio_set_value(gpio_rd_pcb_frame, 1);
  gpio_set_value(gpio_nsol_frame, 1);

#endif
  return status;
}


/* -------------------------------------------------------------------------*
* Function   : spiWrBit                                                    *
* Description: This function is used to transfer a bit to SPI bus.         *
* Parameters : None                                                        *
* Return     : None                                                        *
* -------------------------------------------------------------------------*/
u8 spiWrBit(int vbit)
{
  u8 readbit = 0;
#ifdef DEBUG_TEST
  return 1;
#else
//  gpio_set_value(gpio_spi_clk, spi_cpol);
  gpio_set_value(gpio_spi_clk, (spi_cpha^spi_cpol));
  gpio_set_value(gpio_spi_dout, vbit);
  spiDelay(spi_delay_us);
  gpio_set_value(gpio_spi_clk, 1-(spi_cpha^spi_cpol));
  spiDelay(spi_delay_us);
  readbit = (gpio_get_value(gpio_spi_din) >> (gpio_spi_din % 32));
  gpio_set_value(gpio_spi_clk, spi_cpol);
  return readbit;
#endif
}

u8 spi_sendbyte(u8 iSendData)
{
  s8 iCount = 0;
  u8 recvData = 0;

  //printk("<1>""spi_out:%d\n",iSendData);
  for(iCount=7; iCount>=0; iCount--)
  {
    recvData = (recvData<<1) + spiWrBit((iSendData>>iCount) & 1);
  }
#ifdef DEBUG_TEST
  printk("<1>""send:0x%02x recv:0x%02x\n",iSendData,recvData);
#endif
  return recvData ;
}

static int spi_open(struct inode *inode, struct file *filp)
{
  /* Init the board addrss */
  int status;
  printk(KERN_ALERT "Init the gpios for SPI.\n");
  status = spi_gpio_init();
  return status;
}
static int spi_release(struct inode *inode, struct file *file)
{
  printk(KERN_ALERT "close the gpios for SPI.\n");
  gpio_free(gpio_spi_cs);
  gpio_free(gpio_spi_clk);
  gpio_free(gpio_spi_dout);
  gpio_free(gpio_spi_din);
  gpio_free(gpio_nDsp_rst);
  gpio_free(gpio_nFpga_rst);
  gpio_free(gpio_Fpga_rtrig);
  gpio_free(gpio_nFpga_rdata);
  gpio_free(gpio_wr_pcb_frame);
  gpio_free(gpio_rd_pcb_frame);
  gpio_free(gpio_nsol_frame);
  return 0;
}
static ssize_t spi_read(struct file *filp, char __user *buf, size_t count, loff_t *offp)
{
  unsigned long		missing;
  if (count > BUFSIZE)
    return -EMSGSIZE;
  //memset(rev_buf, '\0', BUFSIZE);
  missing = copy_to_user(buf, recv_buf, count);
  if (missing !=0 )
    return -EFAULT;
  return 0;
}

static ssize_t spi_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
  //  struct spidev_data	*spidev;
  ssize_t		status = 0;
  unsigned long		missing = 0;
//  unsigned long         irq_flags = 0;
  int                   i = 0;
  /* chipselect only toggles at start or end of operation */
//  if (count > BUFSIZE)
//    return -EMSGSIZE;

  //  spidev = filp->private_data;

  //  mutex_lock(&spidev->buf_lock);
  
  local_irq_save(irq_flags);    // turn off all irqs;

  gpio_set_value(gpio_spi_clk, spi_cpol);


  if(spi_WR & 1)                    // make the wr or rd signal low
    gpio_set_value(gpio_wr_pcb_frame, 0);
  if(spi_WR & 2)
    gpio_set_value(gpio_rd_pcb_frame, 0);   
  if(spi_WR == 4)
    gpio_set_value(gpio_nsol_frame, 0); 

  if(count > BUFSIZE) //if the count is more than 32 byte, send byte by byte
  {
    for(i = 0;i<count;i++)
    {
      missing = copy_from_user(send_buf, buf+i, 1);
      if(missing == 0)
        recv_buf[0] = spi_sendbyte(send_buf[0]);
      spiDelay(spi_byte_delay);
    }
  }
  else if(count == 32)
  {

    missing = copy_from_user(send_buf, buf, count);
    if(missing == 0)
      for(i = 0;i<count;i++)
      {
        recv_buf[i] = spi_sendbyte(send_buf[i]);
        if(i%2 == 0)
          continue;
        spiDelay(spi_byte_delay);
      }
/*
    spiDelay(5);
    gpio_set_value(gpio_nsol_frame, 0);//send nsol valve signal
    send_buf[0] = 0xff;
    spi_sendbyte(send_buf[0]);
*/
  }
  else
  {
    missing = copy_from_user(send_buf, buf, count);
    if(missing == 0)
      for(i = 0;i<count;i++)
      {
        recv_buf[i] = spi_sendbyte(send_buf[i]);
        spiDelay(spi_byte_delay);
      }
  }
  if(spi_WR > 0)
  {
    spi_WR = 0;
    gpio_set_value(gpio_wr_pcb_frame, 1);// reset wr and rd signal
    gpio_set_value(gpio_rd_pcb_frame, 1);
    gpio_set_value(gpio_nsol_frame, 1);// reset sol valve signal
  }

  gpio_set_value(gpio_spi_clk, spi_cpol);
  if(missing != 0)
    status = -EFAULT;

  local_irq_restore(irq_flags); //turn on all irqs;
  //  mutex_unlock(&spidev->buf_lock);
  return status;
}
static long spi_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
  long retval = 0;
  switch(cmd)
  {
    case SPI_SET_kHZ:
      if(arg>0)
        spi_delay_us = 500/arg;  //( 1000000 us /(2*arg*1000 Hz) )
      else
        spi_delay_us = 3; //default: one pulse lasts 3*2 us 
      break;
#ifndef DEBUG_TEST
    case SPI_SET_CPHA:
      if(arg>0)
        spi_cpha = 1;
      else
        spi_cpha = 0;
      break;
    case SPI_SET_CPOL:
      if(arg>0)
        spi_cpol = 1;
      else
        spi_cpol = 0;
      gpio_set_value(gpio_spi_clk, spi_cpol);
      break;
    case NFPGA_RST:
      if(arg>0)
        gpio_set_value(gpio_nFpga_rst, 1);
      else
        gpio_set_value(gpio_nFpga_rst, 0);
      break;
    case FPGA_RTRIG:
      if(arg>0)
        gpio_set_value(gpio_Fpga_rtrig, 1);
      else
        gpio_set_value(gpio_Fpga_rtrig, 0);
      break;
    case NDSP_RST:
      if(arg>0)
        gpio_set_value(gpio_nDsp_rst, 1);
      else
        gpio_set_value(gpio_nDsp_rst, 0);
      break;
    case SPI_CLK:
      if(arg>0)
        gpio_set_value(gpio_spi_clk, 1);
      else
        gpio_set_value(gpio_spi_clk, 0);
      break;
    case SPI_SET_WR:
        spi_WR = arg;
      break;
    case IRQ_TURN:
      if(arg>0)
      {
        local_irq_restore(irq_flags); //turn on all irqs;
        printk(KERN_ALERT "turn on all irqs\n");
      }
      else
      {
        local_irq_save(irq_flags);  // turn off all irqs;
        printk(KERN_ALERT "turn off all irqs\n");
      }
      break;

#endif
    case SPI_SET_DELAY:
      spi_byte_delay = arg;
      break;
    default:
      break;
  }
  return retval;
}

static int spi_create_dev(void)
{
  struct device *dev;
  int status;
  spidev_class = class_create(THIS_MODULE, DEVICE_NAME); //register a class to allow "mdev" in "/dev/"
  if (IS_ERR(spidev_class)) {
    return PTR_ERR(spidev_class);
  }

  dev = device_create(spidev_class,NULL, MKDEV(spi_major, 0), NULL, DEVICE_NAME);

  status = IS_ERR(dev) ? PTR_ERR(dev) : 0;

  return status;
}
static struct file_operations spi_fops = {
  .owner = THIS_MODULE,
  .open  = spi_open,
  .read  = spi_read,
  .write = spi_write,
  .unlocked_ioctl = spi_ioctl,
  .release = spi_release,
};

static int __init spi_init(void)
{
  int res;
  dev_t devon = MKDEV(spi_major, 0);
  spi_release(NULL,NULL);
  if(spi_major)
    res = register_chrdev_region(devon, 1, DEVICE_NAME);
  else
    res = alloc_chrdev_region(&devon, 0, 1, DEVICE_NAME);
  if(res < 0)	{
    printk(KERN_ALERT "ERROR: SPI cannot get device number!\n");
    return res;
  }

  spi_major = MAJOR(devon);
  spi_minor = MINOR(devon);

  cdev_init(&spi_dev, &spi_fops);
  spi_dev.owner = THIS_MODULE;
  spi_dev.ops = &spi_fops;

  if( cdev_add(&spi_dev, devon, 1) < 0)	{
    printk(KERN_ALERT "Add the spi device error\n");
    unregister_chrdev_region(devon, 1);
    return -1;
  }

  //printk(KERN_ALERT "You must create the dev file manually.\n");
  //printk(KERN_ALERT "TODO: mknod /dev/%s c %d %d\n",DEVICE_NAME, spi_major, spi_minor);
  if(spi_create_dev() < 0)
  {
    printk(KERN_ALERT "can not create the spi device file in /dev/\n");
    unregister_chrdev_region(devon, 1);
    return -1;
  }
  printk(KERN_ALERT "TODO: device file has been made: /dev/%s\n",DEVICE_NAME);

  return 0;
}

static void __exit spi_exit(void)
{
  printk(KERN_ALERT "The SPI driver exit!\n");
  device_destroy(spidev_class,MKDEV(spi_major, 0));
  class_destroy(spidev_class);
  cdev_del(&spi_dev);
  unregister_chrdev_region(MKDEV(spi_major, 0), 1);
}

module_init(spi_init);
module_exit(spi_exit);

MODULE_AUTHOR("WOW");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SPI driver for gpio");
